// CHAPTER 3 OF OBJECT ORIENTED JAVASCRIPT
// FUNCTIONS
mainchapter("3 FUNCTIONS");

(function(){
	// 3.1 Calling a function
	chapter("3.1 Define / Call a function");
	try{
		function sum(a,b){
			var c = a + b;
			return c;
		}
		success("3.1a Define a function");
		assertEquals(3,sum(1,2));
		assertEquals(NaN,sum(1));
		assertEquals(NaN,sum(1));
		assertEquals(3,sum(1,2,3,4,5));
		success("3.1b Call a function");
		function args(){ return arguments;} // possible thanks to the arguments array that is created inside each function
		var myArgs = args("test");
		assertEquals(myArgs[0],"test");
		function sumOnSteroids(){
			var i, res = 0;
			var number_of_params = arguments.length;
			for(i=0;i<number_of_params;i++){
				res+=arguments[i];
			}
			return res;
		}
		assertEquals(sumOnSteroids(1,2,3,4,5),15);
		success("3.1c Array of arguments available in function");
		try{
			// eval("this_is_not_a_function();"); // narcissus stops executing after this line
			// $ERROR("Calling an undefined function throws no error");
			$ERROR("Narcissus crashes with undefined_function_call(); (also when surrounded with try/catch)");
			$ERROR("Narcissus crashes with eval('undefined_function_call();');");
		}
		catch(e){
			success("3.1e Can not call an undefined function");
		}
	}catch(e){
		$ERROR("3.1 Define / Call a function : "+e.message);
	}
})();

(function(){
	chapter("3.2 BUILTIN FUNCTIONS (DONE)");
})();

(function(){
	// 3.3 SCOPE OF VARIABLES
	chapter("3.3 SCOPE OF VARIABLES");
	try{
		var global_var = 1;
		var f = function() {
			var local = 2;
			global_var++;
			return global_var;
		}
		assertEquals(f(),2);
		assertEquals(f(),3);
		success("3.3.1a Access to global property (literal notation)");
		try{
			eval("local;");
			eval("if(local === undefined) throw new AssertError('NOTE: local undefined OK // ERROR THROWN NOK !! @SEE 2.1.3');");
			$ERROR("3.3.1b ACCESSED LOCAL PROPERTY, ERROR EXCPECTED (literal notation)");
		}catch(e){
			success("3.3.1b No access to local property (literal notation): "+e.message);
		}
		var global_var2 = 1;
		function f2() {
			var local2 = 2;
			global_var2++;
			return global_var2;
		}
		assertEquals(f2(),2);
		assertEquals(f2(),3);
		success("3.3.1c Access to global property (normal notation)");
		assertEquals(typeof local2,"undefined");
		success("3.3.1d No access to local property (normal notation)");
	}catch(e){
		$ERROR("3.3.1 SCOPE PROBLEM : "+e.message);
	}
	try{
		var a = 123;
		function f(){
			assertEquals(a,undefined);
			var a = 1;
			assertEquals(a,1);
		}
		f();
		assertEquals(a,123);
		success("3.3.2 Variable name reused");
	}catch(e){
		$ERROR("3.3.2 Variable name reused : "+e.message);
	}
})();

(function(){
	// 3.4 FUNCTIONS AS DATA
	chapter("3.4 FUNCTIONS AS DATA");
	try{
		function f(){return 1;}
		assertEquals(typeof f,"function");
		success("3.4a normal function notation");
		var f2 = function(){return 1;}
		assertEquals(typeof f2,"function");
		success("3.4b function literal notation");
		// You can use the delete operator to delete variables declared implicitly but not those declared with the var statement.
		sum = function(a,b){return a+b;}
		add = sum;
		delete sum;
		assertEquals(typeof sum,"undefined","sum must be undefined");
		assertEquals(typeof add,"function","add is not a function");
		assertEquals(add(1,2),3,"add is not working properly");
	}catch(e){
		$ERROR("3.4 FUNCTION AS DATA : "+e.message);
	}
})();

(function(){
	// 3.5 CALLBACK FUNCTIONS
	chapter("3.5 CALLBACK FUNCTIONS");
	try{
		function invoke_and_add(a, b){
			return a() + b();
		}
		function one(){
			return 1;
		}
		function two(){
			return 2;
		}
		assertEquals(invoke_and_add(one,two),3);
		success("3.5a functions as paramaters");
		function addOne(a){return a+1;}
		function multiplyByTwo(a, b, c, callback)
		{
			var i, ar = [];
			for(i = 0; i < 3; i++)
			{
				ar[i] = callback(arguments[i]*2);
			}
			return ar;
		}
		var nr_list = multiplyByTwo(1,2,3,addOne);
		assertEquals(nr_list[0],3);
		assertEquals(nr_list[2],7);
		success("3.5b callback function with paramaters");
	}catch(e){
		$ERROR("3.5 CALLBACK FUNCTIONS : "+e.message);
	}
})();

(function(){
	// 3.6 SELF INVOKING FUNCTIONS
	chapter("3.6 SELF INVOKING FUNCTIONS");
	try{
		var a = (function(){
			return 1;
		})();
		assertEquals(a,1);
		success("3.6 function called automatically");
	}catch(e){
		$ERROR("3.6 Function not called : "+e.message);
	}
})();

(function(){
	// 3.7 inner (Private) Functions
	chapter("3.7 inner (Private) Functions");
	try{
		var a = function(param){
			var localf = function(theinput){
				return theinput * 2;
			};
			return localf(param);
		};
		assertEquals(a(2),4);
		success("3.7a inner function executed in scope");
		assertEquals(typeof localf,"undefined");
		success("3.7b inner function unaccessible out of scope");
	}catch(e){
		$ERROR("3.7 inner (Private) Functions : "+e.message);
	}
})();

(function(){
	// 3.8 FUNCTIONS RETURN FUNCTIONS
	chapter("3.8 FUNCTIONS RETURN FUNCTIONS");
	try{
		var result = 1;
		var literal_a = function(){
			result = 10;
			return function(){
				result = 20;
			}
		}
		var fn_a = literal_a();
		assertEquals(result,10);
		fn_a();
		assertEquals(result,20);
		success("3.8a functions returned correct (literal notation)");
		literal_a = literal_a();
		assertEquals(result,10);
		literal_a();
		assertEquals(result,20);
		success("3.8b function rewrites thyself");
		var result2 = 1;
		function normal_a(){
			result2 = 10;
			return function(){
				result2 = 20;
			}
		}
		var fn_a = normal_a();
		assertEquals(result2,10);
		fn_a();
		assertEquals(result2,20);
		success("3.8c functions returned correct (normal notation)");
	}catch(e){
		$ERROR("3.8 function returns function : "+e.message);
	}
})();

(function(){
	// 3.9 CLOSURES IN FUNCTIONS
	chapter("3.9 SCOPE / CLOSURES IN FUNCTIONS");
	try{
		var a = 1; 
		var f = function(){var local = 1;return a;}
		assertEquals(f(),1);
		success("3.9.1a global variable updated");
		assertEquals(typeof local,"undefined");
		success("3.9.1b local variable unaccessible");
		var f1 = function(){var local_a = 1; return f2();}
		var f2 = function(){return local_a;}
		try{
			var result = f1();
			try{
				assertEquals(typeof result,"undefined");
				success("3.9.1c local variable is undefined // NOTE: NO ERROR WAS THROWN WHILE EXECUTED");
			}catch(e){
				$ERROR("3.9.1c FUNCTION EXECUTED WITH undefined Variable");				
			}
		}catch(e){
			success("3.9.1c Function with unknown var > ERROR : "+e.message);
		}
		// You can use the delete operator to delete variables declared implicitly but not those declared with the var statement.
		local_a = 5; 
		assertEquals(f1(),5);
		local_a = 55;
		assertEquals(f1(),55);
		success("3.9.1d var introduced after function");
		delete local_a;
		try{
			var result = f1();
			try{
				assertEquals(typeof result,"undefined");
				success("3.9.1e DELETED variable is undefined // NOTE: NO ERROR WAS THROWN WHILE EXECUTED");
			}catch(e){
				$ERROR("3.9.1e FUNCTION EXECUTED WITH DELETED Variable");				
			}
		}catch(e){
			success("3.9.1e Function with deleted var > ERROR : "+e.message);
		}
	}catch(e){
		$ERROR("3.9.1 SCOPE IN FUNCTIONS : "+e.message);
	}	
	try{
		var f = function(){
			var b = "b";
			return function(){
				return b;
			}
		}
		assertEquals(typeof b, "undefined");
		var n = f();
		assertEquals(n(),"b");
		success("3.9.2a CLOSURE example 1")
		var n;
		var f2 = function(){
			var b = "b";
			n = function(){
				return b;
			}
		}
		assertEquals(n(),"b");
		success("3.9.2b CLOSURE example 2");
		var f3 = function(arg){
			var n = function(){
				return arg;
			};
			arg++;
			return n;
		}
		var m = f3(123);
		assertEquals(m(),124);
		assertEquals(m(),124);
		success("3.9.2c CLOSURE example 3");
	}catch(e){
		$ERROR("3.9.2 CLOSURES IN FUNCTIONS : "+e.message);
	}
	try{
		var f = function(){
			var a = [];
			var i;
			for(i = 0; i < 3; i++)
			{
				a[i] = function(){
					return i;
				}
			}
			return a;
		}
		var a = f();
		assertEquals(a[0](),3);
		assertEquals(a[1](),3);
		assertEquals(a[2](),3);
		success("3.9.3a CLOSURE IN A LOOP example 1");
		var f = function(){
			var a = [];
			var i;
			for(i = 0; i < 3; i++)
			{
				a[i] = (function(x){
							return function(){
								return x;
							}
				})(i);
			}
			return a;
		}
		var a = f();
		assertEquals(a[0](),0);
		assertEquals(a[1](),1);
		assertEquals(a[2](),2);
		success("3.9.3b CLOSURE IN A LOOP example 2");
	}catch(e){
		$ERROR("3.9.3 CLOSURES IN A LOOP : "+e.message);
	}
})();


(function(){
	// 3.10 FUNCTION EXAMPLES
	chapter("3.10 FUNCTION EXAMPLES");
	try{
		var getValue,setValue;
		(function(){
			var secret = 0;
			getValue = function(){
				return secret;
			};
			setValue = function(v){
				secret = v;
			};
		})();
		assertEquals(getValue(),0);
		setValue(123);
		assertEquals(getValue(),123);
		success("3.10a GETTER/SETTER");
		var setup = function(x){
			var i = 0;
			return function(){
				return x[i++];
			};
		}
		var next = setup(['a','b','c']);
		assertEquals(next(),'a');
		assertEquals(next(),'b');
		assertEquals(next(),'c');
		success("3.10b Iterator");
	}catch(e){
		$ERROR("3.10 FUNCTION EXAMPLES : "+e.message);
	}
})();




